﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace IEx.Common
{
    internal class WebResponseEncoding
    {
        public string Charset
        {
            private set;
            get;
        }

        private WebRequest Request;

        /// <summary>
        /// Create an instance of <see cref="WebResponseEncoding"/> with Charset is detected automatically from the web response
        /// </summary>
        public WebResponseEncoding(WebRequest webRequest)
            : this(webRequest, null)
        {
        }

        /// <summary>
        /// Create an instance of <see cref="WebResponseEncoding"/> with defined Charset. WebResponse will use this Charset to encode response html
        /// </summary>
        /// <param name="webRequest">The request to get response</param>
        /// <param name="charset">User defined charset</param>
        public WebResponseEncoding(WebRequest webRequest, string charset)
        {
            this.Charset = charset;
            this.Request = webRequest;
        }

        /// <summary>
        /// Get response html 
        /// </summary>
        /// <returns></returns>
        public string GetResponse()
        {
            HttpWebResponse response = this.Request.GetResponse() as HttpWebResponse;
            if (response == null)
                return null;
            /*
             * Referent: http://www.w3.org/TR/html4/charset.html
             * Conforming user agents must observe the following priorities when determining a document's character encoding (from highest priority to lowest):
             * 1. An HTTP "charset" parameter in a "Content-Type" field.
             * 2. A META declaration with "http-equiv" set to "Content-Type" and a value set for "charset".
             * 3. The charset attribute set on an element that designates an external resource.
             */

            // firstly, try to get charset from response header
            string pageCharset = this.Charset;
            if (string.IsNullOrEmpty(pageCharset) && !string.IsNullOrEmpty(response.Headers["Content-Type"]))
            {
                Match m = Regex.Match(response.Headers["Content-Type"], "charset=([^\"'>]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                if (m != null && m.Success && m.Groups.Count > 1)
                    pageCharset = m.Groups[1].Value;
            }

            MemoryStream memoryStream = new MemoryStream();
            using (Stream responseStream = response.GetResponseStream())
            {

                byte[] buffers = new byte[1024];
                int byteCount = 0;
                do
                {
                    byteCount = responseStream.Read(buffers, 0, buffers.Length);
                    memoryStream.Write(buffers, 0, byteCount);
                } while (byteCount > 0);
            }
            string html = "";
            StreamReader str = null;
            if (string.IsNullOrEmpty(pageCharset))
            {
                memoryStream.Seek(0, SeekOrigin.Begin);
                str = new StreamReader(memoryStream, Encoding.GetEncoding(response.CharacterSet) ?? Encoding.Unicode);

                html = str.ReadToEnd();
                // Get charset from <META http-equiv="Content-Type" content="text/html; charset=utf-16">
                MatchCollection matches = Regex.Matches(html, "(?<=<meta(.*)charset=)([^\"'>]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                foreach (Match m in matches)
                {
                    if (m.Success)
                    {
                        pageCharset = m.Groups[2].Value;
                        break;
                    }
                }
            }

            if (string.IsNullOrEmpty(pageCharset))
            {
                // <meta charset="utf-8" />
                MatchCollection matches = Regex.Matches(html, "(?<=<meta(.*)charset=\")([^\"'>]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                foreach (Match m in matches)
                {
                    if (m.Success)
                    {
                        pageCharset = m.Groups[2].Value;
                        break;
                    }
                }
            }

            if (string.IsNullOrEmpty(pageCharset))
                pageCharset = string.IsNullOrEmpty(response.CharacterSet) ? "utf-8" : response.CharacterSet;

            Encoding encoding = null;
            try
            {
                encoding = Encoding.GetEncoding(pageCharset);
            }
            catch (ArgumentException)
            {
                encoding = Encoding.Default;
            }

            memoryStream.Seek(0, SeekOrigin.Begin);
            str = new StreamReader(memoryStream, Encoding.GetEncoding(pageCharset));

            string returlHtml = str.ReadToEnd();
            memoryStream.Close();

            return returlHtml;
        }
    }
}
